package main

import (
	_ "image/png"
	// _ "image/jpeg"
	"log"
	"fmt"
	"time"
	"image/color"
	"math"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/ebitenutil"
	"github.com/hajimehoshi/ebiten/v2/inpututil"
)

var (
	catimg *ebiten.Image
	dogimg *ebiten.Image
	fishbone *ebiten.Image
	dogbone *ebiten.Image
	dustbin *ebiten.Image
	dogfood *ebiten.Image
	wall *ebiten.Image
	ground *ebiten.Image
	sun *ebiten.Image
	bomb *ebiten.Image
)


const CAT bool = true
const DOG bool = false
const SCREEN_WIDTH int = 320
const SCREEN_HEIGHT int = 240
// 三角函数周期T = 2 * pi / omiga
const omiga float64 = 0.002

func init() {
	catimg = loadImg("img/cat.png")
	dogimg = loadImg("img/dog.png")
	fishbone = loadImg("img/fishbone.png")
	dogbone = loadImg("img/bone.png")
	dustbin = loadImg("img/dustbin.png")
	dogfood = loadImg("img/dogfood.png")
	wall = loadImg("img/wall.png")
	ground = loadImg("img/ground.png")
	sun = loadImg("img/sun.png")
	bomb = loadImg("img/bomb.png")
}

func loadImg(filename string) *ebiten.Image{
	img, _, err := ebitenutil.NewImageFromFile(filename)
	if err != nil {
		log.Fatal(err)
	}
	return img
}

type State int
const (
	WaitPressKey State = iota
	PressingKey
	Throwing
)

// 被打倒的伤害效果
type Hit int
const (
	None Hit = iota
	Missed
	Blocked
	Rub
	Body
	Head
)

func (h Hit) String() string {
	switch h {
	case Missed:
		return "Missed"
	case Blocked:
		return "Blocked"
	case Rub:
		return "Rub"
	case Body:
		return "Body"
	case Head:
		return "Head"
	default:
		panic("undefined Hit")
	}
}

func (s State) String() string {
	switch s {
	case Throwing:
		return "Throwing"
	case WaitPressKey:
		return "WaitPressKey"
	case PressingKey:
		return "PressingKey"
	default:
		panic("undefined state")
	}
}

type Rock struct {
	x, y int
	// 轨迹函数: (x-tx)^2 = 2p(y-ty)
	// p, tx, ty int
	speed int
	// 三点确定的轨迹函数
	x0, y0, x1, y1, x2, y2 int
}

// func (r *Rock) calcuY() {
// 	r.y = int(math.Pow(float64(r.x - r.tx), 2) / (2 * float64(r.p)) - float64(r.ty))
// }

// Lagrangepolynomial
func (r *Rock) calcuY() {
	var fx0, fy0, fx1, fy1, fx2, fy2 = float32(r.x0), float32(r.y0), float32(r.x1), float32(r.y1), float32(r.x2), float32(r.y2)
	var fx = float32(r.x)
	r.y = int(fy0 * ljx(fx, fx0, fx1, fx2) + fy1 * ljx(fx, fx1, fx0, fx2) + fy2 * ljx(fx, fx2, fx1, fx0))
}

func ljx(x, xj, x1, x2 float32) float32 {
	return (x - x1) / (xj - x1) * (x - x2) / (xj - x2)
} 

type Game struct{
	turn bool  // true for cat, false for dog
	state State  // 鼠标交互的
	hitResult Hit

	rock Rock
	timer time.Time
	mx, my int
}

func convertMillisecond(timeTime time.Time) int {
	var seconds = float64(timeTime.Nanosecond()) / 1e9 + float64(timeTime.Second())
	seconds += float64(timeTime.Minute() * 60 + timeTime.Hour() * 60 * 60)
	return int(seconds * 1000)
}

func (g *Game) Update() error {
	if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
		if g.state == WaitPressKey {
			g.state = PressingKey
			g.hitResult = None
			g.timer = time.Now()
			g.mx, g.my = ebiten.CursorPosition()
		}
	} else if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
		if g.state == PressingKey {
			g.state = Throwing
			var now = time.Now()
			var timebias = convertMillisecond(now)
			timebias -= convertMillisecond(g.timer)
			g.initRock(timebias)
		}
	}
	
	if g.state == Throwing {
		g.updateRock()
		g.checkHitResult()
	}
	return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
	screen.Fill(color.RGBA{0x98, 0xf5, 0xff, 0xff})

	iw, _ := catimg.Size()

	scale := float64(SCREEN_WIDTH) / 6 / float64(iw)

	// ebitenutil.DebugPrint(screen, "Hello, World!")
	catop := &ebiten.DrawImageOptions{}
	catop.GeoM.Scale(scale, scale)
	catop.GeoM.Translate(0, 130)
	screen.DrawImage(catimg, catop)

	dogop := &ebiten.DrawImageOptions{}
	dogop.GeoM.Scale(scale, scale)
	dogop.GeoM.Translate(250, 165)
	screen.DrawImage(dogimg, dogop)

	dustbinop := &ebiten.DrawImageOptions{}
	dustbinop.GeoM.Scale(0.2, 0.2)
	dustbinop.GeoM.Translate(-18, 153)
	screen.DrawImage(dustbin, dustbinop)

	dogfoodop := &ebiten.DrawImageOptions{}
	dogfoodop.GeoM.Scale(0.2, 0.2)
	dogfoodop.GeoM.Translate(235, 187)
	screen.DrawImage(dogfood, dogfoodop)

	wallop := &ebiten.DrawImageOptions{}
	wallop.GeoM.Scale(0.3, 0.3)
	wallop.GeoM.Translate(40, 118)
	screen.DrawImage(wall, wallop)

	groundop := &ebiten.DrawImageOptions{}
	groundop.GeoM.Scale(0.8, 0.8)
	groundop.GeoM.Translate(0, 220)
	screen.DrawImage(ground, groundop)

	sunop := &ebiten.DrawImageOptions{}
	sunop.GeoM.Scale(0.1, 0.1)
	sunop.GeoM.Translate(250, 0)
	screen.DrawImage(sun, sunop)

	if g.state == Throwing {
		drawRock(screen, &g.rock, g.turn)
	} else if g.state == PressingKey {
		drawBias(screen, g.timer)
	}

	if g.hitResult == Head {
		t := time.Now()
		elapsed := t.Sub(g.timer)
		if elapsed.Seconds() < 1 {
			bombop := &ebiten.DrawImageOptions{}
			bombop.GeoM.Scale(0.2, 0.2)
			if g.turn == DOG {
				// 狗被砸
				bombop.GeoM.Translate(265, 165)
			} else {
				bombop.GeoM.Translate(0, 110)
			}
			screen.DrawImage(bomb, bombop)
		}
	}

	ebitenutil.DebugPrint(screen, fmt.Sprintf("current state %s", g.state))
	ebitenutil.DebugPrint(screen, fmt.Sprintf("\ncursor: position: (%d, %d)", g.mx, g.my))
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
	return SCREEN_WIDTH, SCREEN_HEIGHT
}

// timeBias is the milliseconds since last press
func (g *Game) initRock(timeBias int) {
	var bias int = int(100 * math.Sin(omiga * float64(timeBias)))  // 在-100 ～ 100之间
	if g.turn == CAT {
		// todo
		// var xbias = timeBias % 100 - 50
		// var ybias = timeBias % 50 - 25
		g.rock.x = 20
		g.rock.x0 = 20  // 出手点
		g.rock.y0 = 130
		g.rock.x2 = 280 + bias  // 落点
		g.rock.y2 = 140 + bias / 2
	} else {
		g.rock.x = 285
		g.rock.x0 = 27 - bias
		g.rock.y0 = 130 - bias / 2
		g.rock.x2 = 285
		g.rock.y2 = 192
	}
	// 最高点
	g.rock.x1 = 160
	g.rock.y1 = 10 - bias / 3

	g.rock.speed = 3 + bias / 100
	g.rock.calcuY()
}

func (g *Game) updateRock() {
	if g.turn == CAT {
		g.rock.x += (3 + g.rock.speed)
	} else {
		g.rock.x -= (3 + g.rock.speed)
	}
	g.rock.calcuY()
}

func (g *Game) checkHitResult() {
	if g.turn == CAT {
		if g.rock.x > SCREEN_WIDTH || g.rock.y > SCREEN_HEIGHT { // 界外
			g.hitResult = Missed
		} else if g.rock.x > 135 && g.rock.x < 170 && g.rock.y > 113 { // 被墙阻挡
			g.hitResult = Blocked
		} else if g.rock.x > 240 && g.rock.x < 320 && g.rock.y > 155 && g.rock.y < 240 { // 命中
			g.hitResult = Head
		}
	} else {
		if g.rock.x < 0 || g.rock.y > SCREEN_HEIGHT {
			g.hitResult = Missed
		} else if g.rock.x < 170 && g.rock.x > 135 && g.rock.y > 119 {
			g.hitResult = Blocked
		} else if g.rock.x < 50 && g.rock.x > 0 && g.rock.y > 130 && g.rock.y < 170 {
			g.hitResult = Head
		}
	}
	if g.hitResult != None {
		g.timer = time.Now()
		g.turn = !g.turn
		g.state = WaitPressKey
		log.Println("Hit result:", g.hitResult)	
	}
}

func drawRock(screen *ebiten.Image, rock *Rock, turn bool) {
	// ebitenutil.DebugPrint(screen, fmt.Sprintf("\ncurrent position x=%d, y=%d", rock.x, rock.y))
	if turn == CAT {
		rockop := &ebiten.DrawImageOptions{}
		rockop.GeoM.Scale(0.2, 0.2)
		rockop.GeoM.Translate(float64(rock.x), float64(rock.y))
		screen.DrawImage(fishbone, rockop)
	} else {
		rockop := &ebiten.DrawImageOptions{}
		rockop.GeoM.Scale(0.1, 0.1)
		rockop.GeoM.Translate(float64(rock.x), float64(rock.y))
		screen.DrawImage(dogbone, rockop)
	}
	// ebitenutil.DrawRect(screen, float64(rock.x), float64(rock.y), 20, 20, color.RGBA{0xFF, 0x00, 0x00, 0xff})
}

func drawBias(screen *ebiten.Image, timer time.Time) {
	bias := convertMillisecond(time.Now()) - convertMillisecond(timer)
	var red uint8 = uint8(125 * math.Sin(omiga * float64(bias)) + 125)
	fmt.Printf("\r%d", red)
	// todo
	ebitenutil.DrawRect(screen, 50, 50, 20, 20, color.RGBA{red, 0x00, 0x00, 0xFF})
}

func NewGame() *Game {
	return &Game{
		turn: true,
		state: WaitPressKey,
		hitResult: None,
		rock: Rock{},
	}
}

func main() {
	ebiten.SetWindowSize(640, 480)
	ebiten.SetWindowTitle("Hello, World!")
	if err := ebiten.RunGame(NewGame()); err != nil {
		log.Fatal(err)
	}
}